// (C) Copyright to Kevin. Applied to MIT LICENSE

#include "ChunkActor.h"

#include "BlockEngineGameMode.h"
#include "ChunkRenderTask.h"


// Sets default values
AChunkActor::AChunkActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	RootComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ChunkMesh"));
	((UProceduralMeshComponent *)RootComponent)->SetMaterial(0, ABlockEngineGameMode::GetInstance()->GetMaterial());
}

AChunkActor::~AChunkActor() {
	delete chunk_data;
	chunk_data = NULL;
	ABlockEngineGameMode::GetInstance()->UnregisterChunk(this);
}

// Called when the game starts or when spawned
void AChunkActor::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AChunkActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (!initialized) return;


	// find neighbor chunks
	AChunkActor* chunk = NULL;
	bool neighbors_changed_mark = false;

#define SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(aa, ab, ac) \
		if(!this->neighbors[ab]) {\
			chunk = gm->FindChunk(chunk_coordinate.aa());\
			if (chunk) {\
				this->neighbors[ab] = chunk;\
				chunk->neighbors[ac] = this;\
				neighbors_changed_mark = true;\
			}\
		}

	ABlockEngineGameMode* gm = ABlockEngineGameMode::GetInstance();
	if (chunk_coordinate.y < 255) { SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetUpper, DIRECTION_UP, DIRECTION_DOWN); }
	if (chunk_coordinate.y > 0) { SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetLower, DIRECTION_UP, DIRECTION_DOWN); }
	SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetXPlus, DIRECTION_X_PLUS, DIRECTION_X_MINUS);
	SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetXMinus, DIRECTION_X_PLUS, DIRECTION_X_MINUS);
	SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetZPlus, DIRECTION_Z_PLUS, DIRECTION_Z_MINUS);
	SERVER_CHUNK_MESSAGE_HANDLER__UPDATE_CHUNK(GetZMinus, DIRECTION_Z_PLUS, DIRECTION_Z_MINUS);

	if (neighbors_changed_mark) {
		MarkChanged();
		return;
	}

	if (changed) {
		changed = false;
		ScheduleUpdate();
	}
}

void AChunkActor::Initialize(FChunkCoordinate chunk_coordinate, ChunkData* chunk_data) {
	if (initialized) {
		UE_LOG(BlockEngine, Log, TEXT("Chunk initialized twice! "));
		return;
	}
	// initialize the blocks?
	this->chunk_coordinate = chunk_coordinate;
	this->chunk_data = chunk_data;

	UE_LOG(BlockEngine, Log, TEXT("Initializing chunk at (%i, %i, %i)"), this->chunk_coordinate.x, this->chunk_coordinate.y, this->chunk_coordinate.z);

	// convert to UE4's Y-up coordinate system
	AttachToActor(ABlockEngineGameMode::GetInstance()->GetChunkContainer(), FAttachmentTransformRules::KeepWorldTransform);

	SetActorLocation(FVector((float) (chunk_coordinate.x << 4), (float)(chunk_coordinate.z << 4), (float)(chunk_coordinate.y << 4)));

	initialized = true;
	ABlockEngineGameMode::GetInstance()->RegisterChunk(this);

	MarkChanged();
}

FChunkCoordinate AChunkActor::GetChunkCoordinate() {
	return chunk_coordinate;
}

ChunkData * AChunkActor::GetChunkData()
{
	return chunk_data;
}

void AChunkActor::ScheduleUpdate()
{
	ABlockEngineGameMode* engine = ABlockEngineGameMode::GetInstance();
	// == schedule a update ==
	ABlockEngineGameMode::GetInstance()->GetThreadPool()->AddQueuedWork(new ChunkRenderTask(chunk_coordinate, this));
}

void AChunkActor::SetChunkBlock(int x, int y, int z, uint16 id, uint16 meta) {
	int index = BlockIndex(x, y, z);
	chunk_data->id[index] = id;
	chunk_data->meta[index] = meta;
}

void AChunkActor::MarkChanged() {
	changed = true;
}

const bool& AChunkActor::IsInitialized() const
{
	return initialized;
}

